%{ 
   open Ast
   open Lexing
   open Printf

   let t1of3 (x,_,_) = x
   let t2of3 (_,x,_) = x
   let t3of3 (_,_,x) = x

let parse_error s =
        let {pos_fname=fn; pos_lnum=lnum; pos_bol=bol; pos_cnum=cnum} = symbol_start_pos() in
  Printf.printf "File \"%s\", line %d, character %d\n" fn lnum (cnum-bol);
  print_endline s;
  flush stdout;; 
%}

%token PLUS MINUS TIMES DIV SEMI COLON COMMA EOF
%token POW CONV
%token PI E SQRT EXP SIN COS ABS
%token MODULE SIZE FOR T KERNEL PRAGMA INPUT FILTER /* END */
%token LDIRECT RDIRECT EQUALS
%token LPAREN RPAREN
%token LBRACE RBRACE LBRACK RBRACK DOT
%token <float> FLOAT
%token <int> INT
%token <string> ID
%token <int> PARAM

%left LDIRECT
%left PLUS MINUS
%left TIMES DIV
%left CONV
%left POW
%nonassoc UMINUS

%start program
%type < Ast.expr> expr
%type < Ast.expr> const_int_expr
%type < Ast.expr list> dimensions
%type < Ast.expr> lexpr
%type < Ast.program> program
%type < Ast.expr > for_expr
%type < string list > id_list

%%

program:
/* nothing */                 
{{ p_mdef=[]; p_mdecl=[]; p_adef=[]; p_wdef=[]; p_synap=[]; p_prag=[]; p_indim=[] }}
| program module_def
   {{ p_mdef = $2::$1.p_mdef; p_mdecl = $1.p_mdecl; p_adef = $1.p_adef; p_wdef =
           $1.p_wdef; p_synap = $1.p_synap; p_prag = $1.p_prag; p_indim =
                   $1.p_indim  }}
| program module_decl
   {{ p_mdef = $1.p_mdef; p_mdecl = $2::$1.p_mdecl; p_adef = $1.p_adef; p_wdef =
           $1.p_wdef; p_synap = $1.p_synap; p_prag = $1.p_prag; p_indim =
                   $1.p_indim }}
| program activation_def
   {{ p_mdef = $1.p_mdef; p_mdecl = $1.p_mdecl; p_adef = $2::$1.p_adef; p_wdef =
           $1.p_wdef; p_synap = $1.p_synap; p_prag = $1.p_prag; p_indim =
                   $1.p_indim }}
| program wght_def
   {{ p_mdef = $1.p_mdef; p_mdecl = $1.p_mdecl; p_adef = $1.p_adef; p_wdef =
           $2::$1.p_wdef; p_synap = $1.p_synap; p_prag = $1.p_prag; p_indim =
                   $1.p_indim }}
| program ext_synap_def
   {{ p_mdef = $1.p_mdef; p_mdecl = $1.p_mdecl; p_adef = $1.p_adef; p_wdef =
           $1.p_wdef; p_synap = $2::$1.p_synap; p_prag = $1.p_prag; p_indim =
                   $1.p_indim }}
| program pragma
   {{ p_mdef = $1.p_mdef; p_mdecl = $1.p_mdecl; p_adef = $1.p_adef; p_wdef =
           $1.p_wdef; p_synap = $1.p_synap; p_prag = $2::$1.p_prag; p_indim =
                   $1.p_indim }}
| program input_decl
   {{ p_mdef = $1.p_mdef; p_mdecl = $1.p_mdecl; p_adef = $1.p_adef; p_wdef =
           $1.p_wdef; p_synap = $1.p_synap; p_prag = $1.p_prag; p_indim =
                   $2::$1.p_indim }}

module_def:
  MODULE ID neurons RDIRECT neurons LBRACE module_body RBRACE
  {{ mod_name= $2; mod_inputs= $3; mod_outputs= $5; 
  mod_neurons= t1of3 $7;
  mod_moddecls = t2of3 $7; 
  mod_synaps = t3of3 $7 }}

pragma:
  PRAGMA LPAREN ID COMMA expr RPAREN SEMI { ($3,$5) }

input_decl:
  INPUT PARAM dimensions SEMI { ($2,$3) }

module_body:
        /* nothing */                 { ([],[],[]) }
| module_body neuron_def    { ( $2 :: t1of3 $1, t2of3 $1, t3of3 $1 ) }
| module_body module_decl   { ( t1of3 $1, $2 :: t2of3 $1, t3of3 $1 ) }
| module_body ext_synap_def     { ( t1of3 $1, t2of3 $1, $2 :: t3of3 $1 ) } 


neuron_def:  
 ID opt_dimensions LDIRECT synap_def SEMI  
 {{ neuro_name=$1; neuro_ind=$2; neuro_syn=$4 }}

synap_def:  
        expr {{ s_expr=$1; s_for=[] }}
      | expr FOR for_list {{ s_expr=$1; s_for=$3 }}

ext_synap_def:  
 external_ref LDIRECT synap_def SEMI   
        {{ s_dest=$1; s_syn=$3}}

external_ref:
  PARAM opt_dimensions       { Param($1,$2) }
| ID DOT ID opt_dimensions  { ExtNeuron( $1,[], $3,$4 ) }

for_list:
  for_expr              {[$1]}
| for_list for_expr     { $2 :: $1 }

for_expr:
  ID EQUALS LBRACK index_expr RBRACK    { ForExpr($1,$4)}

opt_dimensions:
  /* nothing */                 { [] }
| dimensions                    {($1)}

dimensions:      /* uses of dimensions from *declarations* cannot have ranges, BEGIN, or END */
  LBRACK index_list RBRACK      { List.rev $2 }

index_list:
  index_expr                    { [$1] }
| index_list COMMA index_expr      { $3::$1 }

index_expr:
  index_num                                             { ($1)}
  /*
| COLON                                                 {
        Span(Intgr(1),Intgr(1),End) } */
| index_num COLON index_num                             { Span($1,Intgr(1),$3) }
| index_num COLON const_int_expr COLON index_num        { Span($1,$3,$5) }

index_num:
  const_int_expr                        { ($1) }
/*| END                                   { End } */

const_int_expr:
  INT                                   { Intgr($1) }
| ID                                    { Id($1) }
| LPAREN const_int_expr RPAREN          { ($2) }
| const_int_expr PLUS   const_int_expr  { Binop($1, Add, $3) }
| const_int_expr MINUS  const_int_expr  { Binop($1, Sub, $3) }
| const_int_expr TIMES  const_int_expr  { Binop($1, Mul, $3) }
| MINUS const_int_expr %prec UMINUS     { Negate($2) }
| SIZE LPAREN lexpr COMMA INT RPAREN    { SizeOf($3,$5) }

module_decl:
 ID ID opt_dimensions SEMI   {{ modcl_type=$1; modcl_name=$2; modcl_dims=$3 }}

activation_def:
  ID LPAREN ID opt_fparams RPAREN EQUALS expr SEMI 
        {{ act_name=$1; act_params=$4; act_local= $3; act_expr=$7 }} 

wght_def:
  KERNEL ID LPAREN id_list opt_fparams RPAREN EQUALS expr SEMI 
      {{ wght_name=$2; wght_params=$5; wght_ind= List.rev $4; wght_expr=$8 }} 

opt_fparams:
  /* nothing */                 { [] }
| SEMI fparams                       { ($2) }

fparams:
  fparam_list { List.rev $1 }

fparam_list:
  ID EQUALS FLOAT               { [ ($1,$3)] }
| ID EQUALS INT                 { [ ($1,float_of_int $3)] }
| fparam_list COMMA ID EQUALS FLOAT     { ($3,$5) :: $1 }
| fparam_list COMMA ID EQUALS INT     { ($3,float_of_int $5) :: $1 }

neurons:
  neuron_list { List.rev $1 }

neuron_list:
  ID opt_dimensions { [($1,$2)] }
| neuron_list COMMA ID opt_dimensions { ($3,$4)::$1 }

id_list:
  ID          { [$1] }
| id_list COMMA ID { $3 :: $1 }
  
expr:
  lexpr                         { ($1) }
| expr2                         { ($1) }
| expr2 dimensions              { Index($1, $2) }
| expr PLUS   expr              { Binop($1, Add, $3) }
| expr MINUS  expr              { Binop($1, Sub, $3) }
| expr TIMES  expr              { Binop($1, Mul, $3) }
| expr DIV    expr              { Binop($1, Div, $3) }
| expr POW    expr              { Binop($1, Pow, $3) }
| MINUS expr %prec UMINUS       { Negate($2) }
| FLOAT                         { Float($1) }
| INT                           { Intgr($1) }
| expr CONV wghtref             { Conv($1,$3) }
| PI                            { Float(3.141592653589793) }
| E                             { Float(2.718281828459045) }

expr2:
  LPAREN expr RPAREN            { ($2) }
| ID LPAREN expr RPAREN         { ActRef($1,$3,[]) }
| ID LPAREN expr SEMI fparams  RPAREN       { ActRef($1,$3, $5) }
| wghtref                       { ($1) }
| EXP LPAREN expr RPAREN        { Exp($3) }
| SIN LPAREN expr RPAREN        { Sin($3) }
| COS LPAREN expr RPAREN        { Cos($3) }
| ABS LPAREN expr RPAREN        { Abs($3) }
| FILTER LPAREN expr COMMA expr RPAREN { Filter($3,$5,[]) }

lexpr: 
  ID                      { Id($1) }
| ID dimensions           { NeuronRef($1,$2) }
| external_ref            { ExternalRef($1) }

wghtref:
  ID LPAREN RPAREN              { WghtRef($1,[]) }
| ID LPAREN fparams RPAREN  { WghtRef($1,$3) }

